



 |
Charlie Calvert's C++ Builder Unleashed
- 2 -
Basic Facts About
C++Builder
This chapter and the next five cover the basic facts that everyone needs to know
about BCB. Important subjects included in this chapter are as follows:
- The IDE
- Projects
- The VCL
- Core VCL Technologies: Components, Properties, and Events
- Stepping into the Pascal source for the VCL
- The syntax of the VCL
- An overview of the component architecture
- A brief look at Run Time Type Information (RTTI)
This introduction to BCB continues in the next chapter, where I cover the new
Borland additions to the C++ language and introduce several key BCB classes such
as AnsiStrings and Sets. These special classes emulate features
of Object Pascal. Then, in Chapter 4 you will have a look at events. Chapter 5 focuses
on exceptions, Chapter 6 on using Delphi code in BCB, and Chapter 7 on graphics programming
with the VCL. The latter chapter will complete my introduction to the VCL and to
the syntax that is common to almost all BCB programs.
When reading this chapter, you need to remember the basic philosophy of this book.
My goal here is not to plumb the depths of C++, the VCL, the Windows API, or any
other hardcore technical syntax. Instead, I want to show you how to quickly build
real-world applications without losing touch with the underlying Windows architecture.
In these chapters there are many times when I make a pass over very complicated subjects
such as C++ constructors, templates, rules of precedence, the GDI, and so on. I am,
of course, aware that these are sticky subjects that take many pages to cover appropriately.
However, you will not find in-depth discussions of these subjects in this book, for
there are many other volumes dedicated to those topics. Furthermore, the goal of
this book is to show how to use RAD tools and high-level objects to perform complicated
tasks quickly, easily, and safely.
C++ is already at least 10 times faster than interpreted languages such as Visual
Basic, Java, or PowerBuilder. If slowing down performance by five percent yields
a 10- or 20-fold increase in reliability or ease of use, I think it is worth it to
play a somewhat more cautious game. In my opinion, it is better to be nine times
faster and nearly as safe as an interpreted tool than it is to be 10 times faster
and 10 times more dangerous than an interpreted tool. That last 5 or 10 percent that
you can eke out of the language by using every imaginable trick just isn't worth
it, except in a few unusual circumstances such as compilers, operating systems, and
game engines. Even in those cases, it is still probably best to use the relatively
small and fast OOP-based techniques outlined in this book.
This particular chapter is unique in that it covers a number of technical subjects
that are not very complex. Everything in this chapter is here because I either
- Think that you have to know it in order to complete BCB programs.
- Think that you have to know it in order to feel at all comfortable inside the
BCB programming environment.
Don't worry if you find most of the material in this chapter a bit too simplistic.
There is some basic material that almost has to be covered in a book of this type,
and once I get it out of the way, I will move on to more interesting subject matter
in the next chapters.
It's time now to get started with an overview of the BCB environment, project
management, the VCL, and the basic syntax used by BCB programmers. When you have
completed this and the next five chapters, you should have all the knowledge you
need to begin a robust, broad exploration of the all the exciting features found
in BCB.
Creating C++Builder Projects
C++Builder has a project manager that you can access from the View menu. You can
use this tool to add files to a project or to remove files from a project.
You can add files with the following extensions to your project, and C++Builder
will compile and/or link them automatically, as shown in Table 2.1:
Table 2.1. Files you can add to a BCB project.
| Type of File |
Description |
| CPP |
C++ Source module. OWL and MFC are treated in the appendices. |
| C |
C Source module. |
| PAS |
Any Pascal module that will compile in Delphi 2.01. |
| RC |
Resource script. |
| RES |
Resource file. |
| OBJ |
Compiled C++, C, or PAS file. |
| LIB |
C or C++ Library file. |
| DEF |
Module Definition file. |
You can use the IDE to produce Windows executables, DLLs, or console applications:
- To create a 32-bit Windows executable, you need do nothing. The default behavior
of the IDE is to generate this type of executable.
- To create a DLL, go to File | New and select DLL from the Object Repository.
Click OK. Everything else is automatic. I discuss exporting functions from a DLL
in several later chapters of the book, including Chapter 26, "Extending an Internet
Server with ISAPI." In a word, the key to exporting a method from a DLL is to
use __declspec(dllexport), as described in Chapter 26 and in the comments
at the top of the file ISAPI1.CPP from Chapter 26.
- To create a console application, go to the Options | Project | Linker option
from the menu and select Console Application. If you want to create a console application
as a quick way to produce text output, you should consider using the BCB TMemo,
TListBox, and TEdit controls instead. I can output text to these
controls at least as quickly as I can use printf or cout. I find
these components are more useful than outputting text to the command line, because
they support concepts like scrolling and saving their contents to a file or the clipboard.
NOTE: Delphi programmers need to remember
that it is not enough merely to #include a C, CPP, or PAS file in a module
of an existing project. You also have to add the file itself to the project using
the Project Manager or the Add to Project menu choices or speed buttons. Delphi's
linker assumed that you would not reference a file from your uses clause
unless you wanted it to be part of your project. C++Builder, for better or worse,
makes no such assumption. You must incorporate the new file into your makefile listing,
or it will not be linked into your project.
BCB projects are managed in a standard C++ makefile. The easiest way to get something
into your makefile is through the project manager. Editing the makefile itself is
not recommended, but C++ experts will find there are some changes to your project
that can only be made by editing the makefile.
Most of the important changes which can be made to a makefile are configurable
through the Options | Project or Options | Environment menu choices. The developers
of BCB do not expect you to find many occasions when you will need to edit the makefile.
I believe the primary reason the makefile exists is that the team grew tired of trying
to manage a binary project file.
The Microsoft C++ team, on the other hand, recently grew tired of trying to manage
a text-based project file! This is probably one of those cases where developers have
a choice between two evils.
If you are trying to manage projects that consist of multiple executables and
DLLs, you will almost certainly find the current BCB project manager inadequate.
Borland C++ 5.02 will support compiling C++Builder projects. You will therefore want
to consider using the advanced tools in BC 5.02 for managing huge projects.
BC5 also supports a powerful scripting language not available in BCB. As a result,
I think some programmers will find a combination of BC5 and BCB to produce the ultimate
C++ programming environment.
Having made my pitch to that special group of programmers who are managing massive
projects, I want to end this section by stating that I find BCB includes everything
I need and considerably more. The goal of this book is to talk about completing high
quality projects as quickly and efficiently as possible. If that is your goal, stick
with BCB and with third-party tools tailored for this environment. BCB is the ideal
tool for creating C++ applications. It is state of the art and leagues ahead of any
other C++ environment that is planned or available at the time of this writing.
BCB File Extensions
In the last section, in Table 2.1, I list the types of files you can include in
C++Builder. Most of these files will be generated automatically for you by the compiler,
and I list them here just so you will know why they exist and what they do. In this
section, I will talk about all the important files that become part of your project.
Table 2.2 lists the key extensions in BCB projects.
Table 2.2. File types used in a BCB project.
| File extension |
Description |
File type |
| RC |
Source for resource file. |
Text |
| RES |
Resource file. There will usually be
one RES file with the same name
as your project that contains only an icon.
It's best to leave this file alone. |
Binary |
| CPP, C |
C++ source file. |
Text |
| PAS |
Delphi 2.01 source file. |
Text |
| H or HPP |
C++ header file. |
Text |
| DSK |
The location of files on the desktop. |
Text |
| DFM |
Binary file containing form definition. Use
CONVERT.EXE to translate into text. |
Binary |
| MAK |
The project makefile in text format. |
Text |
| TDS |
Turbo debugger symbols. |
Binary |
| ILX |
Incremental linker symbols. |
Binary |
When browsing this table, take special notice of the incremental linker files. These
four files, all of which begin with ILX, are huge. They are the key files needed
to produce BCB's incredibly fast link times. If you turn incremental linking off,
as explained later in this chapter, these files will disappear. Turning incremental
linking off means that your compilation and link times will be from 5 to 10 times
slower than they are with incremental linking turned on.
The include and lib Directory Paths
Issue
There are, confusingly enough, two places where you can designate the paths to
your include and lib files. One is located in the Options | Project
| Directories/Conditionals menu choice, and the second is located in the Options
| Environment | Library section. These pages are shown below in Figures 2.1, 2.2,
and 2.3. There is also a Path for Source option in the Options | Environment | Preferences
page.
FIGURE
2.1. The Options menu leads to the Project
dialog where you can set paths for your project.
FIGURE
2.2. The Options menu is also the gateway
to the Environment dialog where you can find the Library page.
FIGURE
2.3. The Options | Environment | Preferences
page gives you a place to add the path to modules you include in your projects.
The BCB macro shown in the Path statements from Figures 2.1 through 2.3 resolves
into the path that points to your current installation of BCB. This kind of information
is stored in the Registry under HKEY_CURRENT_USER/Software and HKEY_LOCAL_MACHINE/Software.
For instance, see the RootDir entry in HKEY_LOCAL_MACHINE/Software/C++Builder/1.0.
To view the Registry, select Run from the Windows Start menu, type in the word RegEdit,
and press the Enter key.
As a rule, you make changes specific to one project in the Options | Project dialog,
and make global changes that you want reflected in all programs in the Options |
Environment dialog. Use the Path for Source to include any directories that contain
utility code that you use frequently.
As a rule, additions to these various path statements are made automatically when
you add modules to your project through the Project Manager. However, there are times
when I need to go in and explicitly edit one of these options.
Remember that if you are adding a component to the Component Palette, you have
to add the path to that component in the Options Environment dialog or the Component
Palette will not load. This addition to the Path statement will be made automatically
if you add a component from inside the IDE. If you add the component from the command
line by recompiling CMPLIB32.CCL, you need to update the Library path statement
yourself. If you are using DLLs from inside a component, make sure the DLL is in
a directory that is on your global DOS/Windows path. For instance, you might consider
putting it in the Windows or Windows/System directory.
NOTE: The question of whether to call
a C module used in a BCB program a unit or a module is something of an open matter
in BCB. My inclination is to call it a module, because that is traditional C usage,
but BCB seems to refer to them as units. To be utterly frank, this is the kind of
issue that doesn't really grip me all that deeply. In particular, I'm sure you will
have no trouble understanding me regardless of which term I use. As a result, you
will hear me referring to C modules as either modules or units, depending more on
whim than on any clearly defined practice.
Working in the IDE
Here are some tips for working inside the IDE. I'll make this section brief, because
I don't want to waste time on issues like this in a book that is clearly aimed at
experienced programmers. However, this is a new environment, so it might help to
share a few tips.
Whatever you do, be sure that you understand that this tool is meant to be used
from inside its IDE. This is not a command-line environment!
NOTE: I'm sure that most programmers who
investigate the matter will see that the command-line approach makes no sense with
BCB. If that sentence strikes a sour note with you, all I ask is that you don't develop
contempt prior to investigation. In my opinion, this IDE has something so powerful
to offer that it finally makes command-line programming obsolete. With BC5, and even
with MSVC, I usually worked from the command line. I was one of the last of the hardcore
C++ command-line junkies. With BCB, however, I have changed my ways. I'm totally
sold on this environment, and would never consider going back to the command line
except for a few rare situations.
Tips on Manipulating Controls
Here are a few tips for using the visual tools. If you are new to the environment,
you should boot up BCB and follow along when reading these time-saving tips.
When dropping controls on a form, do the following:
- Use the Control key plus the arrow keys to move the location of a component one
pixel at a time. In particular, drop a component such as a button on a form. Select
it with the mouse. Hold down the Control key and press the left arrow key.
- Repeat the previous steps, only this time use Shift plus the arrow keys to resize
a component one pixel at a time.
- Hold down the Shift key when selecting a component from the Component Palette
if you want to drop multiple copies of it on a form without having to go back to
the palette. Click the "arrow" icon at the far left of the Component Palette
to break out of this process.
- Become familiar with the Align and Size options from the Edit menu. Quickly drop
down five buttons on a form, each one below the last, without taking the time to
carefully align their right and left sides. Hold down the Shift key, then select
the five buttons with the mouse by clicking on each one. Now use the Align dialog
from the Edit menu to align the tops or sides of all the selected controls.
- A second technique for selecting a large group of components is to click on the
main form and hold down the left mouse button while dragging over several components.
When you let up the mouse, you will find the components are selected. If the components
you want to select are resting on top of another control such as a panel, then you
will need to hold the control button down before and during the process of dragging
the mouse.
- When designing a form, make it as large as you want. Then, when you have everything
in place, use the Scale option from the Edit menu to make your form as small as you
want, while still keeping each item on the form in proportion.
- Right-click a component, as shown in Figure 2.4, to bring up a menu that lets
you change the size, alignment, tab order, scaling, or creation order.
FIGURE
2.4. Right-clicking a TTable
object to bring up a list of custom options.
Making the Most of the IDE
Here are some tips on using the IDE:
- Right-click almost anything in the IDE to bring up a menu to configure the component
or tool you are currently using. For instance, right-click the colorful toolbar at
the top left of the environment. Select Properties from the popup menu to bring up
the Toolbar editor. Now drag a colorful button from the toolbar onto the Toolbar
editor and grab another button from the ToolBar editor and drag it back onto the
toolbar. I sometimes delete the Trace, Trace Into, and Pause buttons from the extreme
right of the toolbar and replace them with Copy, Cut, and Paste buttons or with compiler
buttons such as Make, Build All, and Compile Unit.
- Notice that you can view the Project Source and Project Makefile from the View
menu. The project source contains the WinMain() or main() block
for your code. You rarely will need to edit this code. Its contents are usually configurable
from the Options | Project menu. For instance, you can change the code in the Project
Source from Forms page of the Options | Project menu.
- Check out the Project Manager, which is also available from the View menu.
- You can lock the controls on a form by selecting the Lock Controls option from
the Edit menu. If you have spent some time working on a form and want to be sure
that it does not accidentally get changed, select Lock Controls, and it will not
be possible to move the controls on that form, or any other, until you deselect this
option.
- If you want to set the text on the editor page to read only, right-click the
editor page and select Read Only.
- Watch out for the AutoScroll property of a form. This is set to true
by default, but it can bite back if you leave it set to true and then move
your form to a different resolution. For instance, if you move from 640x480 to 800x600,
you generally should not have AutoScroll set to true, especially
if your form is populated with a large number of controls.
- Use the Position property of a form to decide whether the form should
first appear in the screen center, at the place where you positioned it at design
time, or at a default location defined by Windows.
- Use the WindowState property of a form to decide if the form should
first be shown minimized, maximized, or at normal size.
- Before releasing a project to the public, check out its appearance in at least
three different resolutions and make sure at least one of them toggles the font from
Small Fonts to Big Fonts, or vice versa. (You can change Windows' resolution by right-clicking
the desktop, selecting Properties, and going to the settings page. Make sure you
change between Big Fonts and Small Fonts during your testing!)
Project Options
Other than the path-related issues covered in the last section, there are only
a few options that you need to know about when programming BCB. The rest of the setup-related
issues are handled for you automatically by the environment.
All the options you choose in the Project and Environment dialogs are written
to the Registry. If you want to write custom programs that change these settings,
you can learn how to proceed by reading the sections on the Registry in Chapter 13,
"Flat-File, Real-World Databases."
The Options | Project menu has six pages in it:
Forms: This page is discussed in depth later in the chapter when I discuss
the Project Source file for the ShapeDem program in the section called "Creating
Forms." The core functionality on this page addresses the question of which
unit will be the main module for your application--that is, which will come up first
when you start the program. A secondary issue addressed in this page is whether a
form will have memory allocated for it automatically at startup, or whether you want
to create it explicitly at some point during your application's runtime. Forms listed
in the left-hand list box shown on this form are created automatically; those on
the right-hand list box must be created explicitly by the developer. The following
code will create a form, show it to the user, and delete it:
Form2 = new TForm2(this);
Form2->ShowModal();
delete Form2;
This code would not work unless the header for Form2 was included in the module
that wanted to call the code quoted here:
#include "unit2.h"
Application: This is where you can set up the icon or help file for your
project. This is an intuitive process; click Help in the dialog if you have questions.
C++: This is where you can set the Debug and Release compiler options. You can
also do a small amount of fine-tuning here, but this book hardly ever steps beyond
recommending that you use the Debug option in development and the Release version
when you ship. I almost never have occasion to do more than choose the simple binary
Debug or Release option, except for occasionally toggling the precompiled headers
option.
Pascal: Here is where Pascal aficionados can fine-tune their code. I would
recommend leaving all these options untouched unless you have a specific reason to
change them. If you want to get involved in this page, the first level of advice
is to turn Range and Stack checking on only during debug cycles, and to turn Optimizations
on only when you ship.
Linker: This is where you can decide to produce a Windows or console application,
an EXE, or a DLL. This is also the place where you can toggle the incremental linker
on and off. In development, you probably want the incremental linker on to speed
compilation; when you ship, you should test the size of your executables when it
is off and when it is on, and ship the smallest version.
Directories / Conditionals: The key features of this page were covered earlier
in this chapter. Note that this is also where you can define conditionals. The whole
subject of unit aliases is an Object Pascal-specific issue that enables you to create
an alias for the name of a unit. For instance, the 16-bit version of Delphi kept
all the Windows API calls in a unit called WinProcs.pas, and all the Windows
types in a unit called WinTypes.pas. When the big segment sizes of 32-bit
Windows became available, the two units were consolidated into one called Windows.pas.
To remove the burden of having to change the uses (#include) statements
in a Pascal file, the developers enabled you to create aliases. The most common alias
told the compiler to use Windows.pas whenever it saw a request to include
the WinTypes or WinProcs units.
As you can see, I don't put a lot of weight on fine-tuning the settings for your
project. If you flip through these pages and see the small number of options available,
you can see that the developers were not very concerned about this issue either.
One of the major goals of BCB is to make C++ once again a mainstream language. The
programming world used to be much simpler than it is today. Now we are all expected
to know about OLE, MAPI, the Internet, multimedia, or other cutting-edge technologies.
I invest my time in learning these valuable new technologies, and ask little more
of my compiler than that it link quickly and easily and automatically produce small,
tight code.
Environment Options
There are six pages in the Options | Environment menu choice. I play with many
of these options all the time because they do nothing more than tweak the appearance
or feel of the IDE. You aren't going to accidentally mess up the link process in
your program or add 500KB to the size of an executable by tweaking one of these options.
Feel free to set them as you please. Following is a list of the options I often play
with during development.
There are some choices that are listed in both the Environment Options pages and
the Project Options pages. If you make a change in the Project pages, you are changing
an option for just that one project, while if you make the change in the Environment
page, you are changing things globally for the entire environment. Local options
always override global options.
Preferences: In the preferences page I always set Show Compiler Progress to
true so that I can tell how far along I am in the compile cycle. I set AutoSave to
true for Desktop files so that the environment will remember which project I was
working on and which files I had open in the IDE. I also frequently tweak Break On
Exception, depending on whether or not I want to catch problems in my code (turn
it on) or just test to see if exceptions are popping up as expected at runtime (turn
it off). This is also where you can turn integrated debugging on and off and change
the path, as described above in the section on setting the project path.
Library: This is where you can set the path for include and lib
files, as described previously. You can also globally decide for all projects whether
or not to use the incremental linker. If you are adding components to the Component
Palette, you should set Save Library source code to true so that you can build the
Component Palette from the command line to save time or to repair a damaged Component
library.
Editor: I discuss this page in a later section called "Feeling at
Home in the IDE." It is here you can customize the behavior of the editor. All
the major third-party editors (CodeWright, SlickEdit, MultiEdit) have some customizations
for BCB, but none of them can get into the environment to the degree to which you,
I, and they would like. Hopefully, improvements will come in this area in later releases.
Display: I discuss this page in a later section called "Feeling at
Home in the IDE." It is here you can choose the keystroke emulation and font
for the editor.
Colors: I discuss this page in a later section called "Feeling at
Home in the IDE." It is here you can customize the colors of the editor. It
particular, it enables you to switch between different color schemes or customize
the colors for each element in the language, such as string, identifiers, integers,
and so on. Like all the settings mentioned in these pages, the results of your decisions
are written to the Registry. The Address2 program from Chapter 13 shows how you could
write custom programs that tweak the Registry. For instance, you could write a program
that automatically switched between four or five additional color schemes.
Palette: If you want to change the order in which components appear in the Component
Palette, this is the place to make your changes. You can also reach this page by
right-clicking the Component Palette and choosing Properties. It is pretty hard to
do any serious damage to the environment using this page, but if you feel you need
help, just press the Help button on the dialog itself.
As you can see, most of the options on these pages address only aesthetic or habit-based
preferences regarding how the IDE works. From a development point of view, the key
issues involve incremental linking, paths, and saving the source for the Component
Palette. Make sure you understand those important issues before moving on to the
next topic of discussion.
Feeling at Home in the IDE
To help make the IDE comfortable, you might go to the Options | Environment |
Editor page, shown in Figure 2.5.
FIGURE
2.5. The Options menu gives you access
to the Environments dialog where you find the Editor page.
From the Editor page you can make the following changes:
- Turn the Use tab character option on or off, depending on your liking. (I prefer
to turn it off, so I always know exactly what my code will look like regardless of
the editor or tab settings I happen to use.)
- Decide what tab stops you want to use. I set mine to 3 and 5.
- Choose the Editor speed setting you want. You can choose between the Default
keymapping, IDE Classic, Brief, or Epsilon emulations.
- Go to the Colors page and set the colors you want to use in the editor.
- Consider setting Undo after save to true so that you can keep a buffer of changes
you make, even after you save.
- There are third-party tools such as SlickEdit, MultiEdit, and CodeWright that
have some degree of integration with C++Builder. However, none of these products
is able to work as closely with the IDE as one would like, due to limitations in
the current Tools API for BCB.
Converting Forms to Text and Back
Everything you can do in BCB through the visual tools you can also do in code.
The visual tools are just a means of expediting the programming process. They do
not supplant code, they complement it. This is what the Borland marketing department
means when they talk about "two way tools." The are two different ways
to approach some parts of a BCB project: in code or by using the RAD tools.
If you right-click a form, you can select the View as Text menu item to convert
a form to text. To convert back, just right-click the text version of the form.
BCB also ships with a command-line utility called Convert that will convert DFM
files to text, or text to DFM files. At the command line type either
convert MyForm.dfm
or
convert MyForm.txt
The first example converts a binary form to a text form with the extension TXT,
and the second example reverses the process.
If you have 4DOS on your system, you can use the following command to convert
all the DFM files in a branch of subdirectories from DFM to text files:
global /I convert *.dfm
This command will iterate through all the subdirectories below your current position
and convert all the files in those directories to text. If you are concerned about
archiving files, this is a good way to proceed. In particular, a text file is a much
more robust storage medium than a binary file. If you lose one byte of a binary file,
it may become worthless. Losing one byte from a text file rarely causes any serious
mischief.
If you have one form and want to paste all or part of it into a second form, you
can select multiple objects from the first form, choose Edit | Copy, focus the second
form, and then paste the selections from the first form into it. If you want, you
can have an intermediate step where you paste the items from the first form into
a text editor such as Notepad, edit them, and then paste them onto a form.
NOTE: If you are a Delphi programmer and
want to port a form from Delphi to BCB, you might consider using the technique outlined
in the last paragraph as a way to proceed. Of course, you can compile your Delphi
forms directly in BCB, but if you want to port them, just cutting and pasting to
the clipboard is a good way to proceed.
Here is a what a BCB button looks like in text form:
object Button1: TButton
Left = 96
Top = 16
Width = 75
Height = 25
Caption = `Button1'
TabOrder = 0
end
To get this code, I Alt+Tabbed out of my word processor over to BCB, selected
a button on a form, and chose Edit | Copy from the menu. I then Alt+Tabbed back to
my word processor, and chose Edit | Paste. During the process the Windows button
was automatically converted to text.
Here is a second version of the button code that has been slightly modified:
object MyButton: TButton
Left = 1
Top = 16
Width = 75
Height = 25
Caption = `My Button'
TabOrder = 0
end
As you can see, I have changed the name of the button from Button1 to
MyButton, and I have changed the Caption and Left properties.
Now I can select this text in my word processor, Alt+Tab over to BCB, select and
form, and choose Edit | Paste to paste it back into the form. However, this time
it has a new name, a new location, and new caption.
This is what is meant by a two-way tool. I can edit the form using the visual
tools, or I can edit it in a word processor. It works in two different ways, depending
on my current needs.
NOTE: When working with forms, remember
that the currently selected component will be the target for a Paste operation. For
instance, if I have selected a TButton object, and I chose Paste, BCB will
attempt to make the control currently in the clipboard into a child of the button.
In most cases, this is not what I want. Instead, I should first select a form or
a panel, and then paste the controls onto that object. You also want to make sure
the object you are pasting into is big enough to receive the control or controls
you are about to dump from the Clipboard.
You have now made it through the first section of this chapter. In the next section
I am going to switch my approach from a "hot tips" format to a more discursive
style. If you want more information of the type you have seen so far in this chapter,
you should look in the online help or pick up a book aimed at introductory BCB programming
issues. Everyone has to know the kind of information I have been ladling out in the
last few pages, and indeed it is vital information, but it is not the subject matter
of this book. I have included this much only because I feel many of the issues addressed
here are not immediately obvious when you first open BCB, and yet you absolutely
have to know these facts in order to get any serious work done in the environment.
Core Technology: Components, Properties,
Delegation
Many people are confused about Borland C++Builder. They are not used to the idea
of having a RAD tool that works with C++, and they don't know quite what to make
of it when they see it.
Some people think they are seeing a code generator; others think this is a visual
tool meant for programmers who don't want to write code. Some people think they have
found a great tool for building databases, and others a tool for prototyping applications.
There is some truth to all of these ideas, yet they all miss the mark if your
aim is to find the essence of Borland C++. The core pieces of the technology are
threefold:
- Right up front you have components and properties.
- Buried a little deeper you have the delegation model, which involves events and
method pointers.
- Tying the whole picture together are exceptions and a very sophisticated RTTI
system located in a file called TYPINFO.HPP. Most programmers rarely have
to deal with this side of the product, but it is part and parcel of what makes the
tool possible in its current form.
These are things that lie at the core of C++Builder. Don't let anyone else lead
you astray with tales about prototyping or about BCB being a replacement for PowerBuilder.
This tool may in fact perform those roles at times, but that's not what it is all
about.
NOTE: It may be that from a commercial
perspective the majority of programmers will find the database support to be the
most valuable aspect of this tool. Indeed, I spend a large portion of this book covering
that subject. However, the emphasis on databases is market-driven, while the technological
core of the product lies elsewhere.
To get at the heart of BCB, you have to understand components, you have to understand
the delegation model, and you have to understand RTTI. In particular, the first two
points are essential to an understanding of how this product works, while the third
helps you understand why it works.
You have, no doubt, either already noticed or have heard talk about the fact that
BCB has some proprietary extensions to C++. These extensions are there to support
the creation and use of components as well as the associated concepts of properties
and events that make components so powerful.
There was no way to create a product like BCB without extending C++ to support
components, properties, and the delegation model. This is a better way to program,
and there is no force in the world that can suppress it. I have no problem at all
asserting that in five years time, all compilers will support these features and
most programmers will use them by two years from now (1999).
Let me say it one more time, because this is so crucially important: What's key
is the component model, and its reliance on properties and events. Components, properties,
the delegation model. Those are the things that stand at the heart of this technology.
The three tools make it easy to build databases or multimedia applications. To say
that the tool is primarily about building games or databases or Web sites is putting
the cart before the horse. The tool is primarily about components, properties, and
the delegation model. The other strengths of the tool fall out more or less automatically
once the ground work has been laid.
Why the VCL?
Now that you know something about the environment in which BCB exists, it's time
to dig a little deeper and start examining the VCL object model used by BCB. The
VCL (Visual Component Library) is an object-oriented library designed to ease the
process of creating visual components.
NOTE: When I say that BCB uses the VCL,
I mean for the phrase to be taken in at least two distinct ways. BCB uses the VCL
in the sense that the physical IDE is literally built into VCL, and also in the sense
that we, as BCB programmers, use the VCL as the object model of choice when creating
applications. Borland is not asking you to do anything they wouldn't do. Delphi is
built into the VCL. BCB is built into the VCL, and much of Latte, the new Java product,
is built into the VCL. The VCL is the tool of choice for people who have a choice.
Many C++ programmers who come to C++Builder find themselves wondering why the
VCL exists in the first place. What was wrong with OWL or with MFC? Why should there
be yet another object framework?
The simple answer is that visual programming, RAD, needed a whole new framework
with new features. RAD relied on new concepts such as event handlers, properties,
property editors, components, component editors, experts, forms, and a slew of other
features. The language that implemented these new syntactical elements also desperately
needed improvements in the areas of streaming, string handling, object initialization,
and referencing.
These features simply were not available in either the C++ or Object Pascal versions
of OWL. As a result, a new framework was created that supported these features; it
is called the VCL, or Visual Component Library. The name goes a long way toward explaining
why OWL could never do this job correctly. This is an object-oriented library built
around visual components, and visual components do not have even the most oblique
reference anywhere in OWL or MFC. They are a completely new entity and required their
own object-oriented framework.
Having said that, I should add that VCL is closely related to OWL, just as the
current version of OWL is closely related to the 16-bit version of OWL from which
it grew. If you know OWL, you will find much in VCL that is familiar. Indeed, even
MFC is a good background for understanding VCL. However, this is a fundamentally
different kind of beast, one that is built around a new concept called a visual component.
NOTE: I am aware, of course, that ActiveX
is another specification for building visual components. The difference between ActiveX
and the VCL is that the VCL is specifically designed to be used with advanced programming
languages such as C++ or Object Pascal. ActiveX was designed to be used in a broader
context featuring a wide variety of languages and operating systems. ActiveX is more
powerful than VCL, but it is also much bigger, much more complex, and slower.
Come on Charlie, Tell Us What You
Really Think!
To conclude this brief introduction to the long discussion of the VCL found in
this chapter, I feel it is important to explicitly state that I am aware that many
hardcore C++ programmers will not automatically greet the VCL and its occasional
bits of nonstandard C++ syntax with open arms. When writing this chapter, I am conscious
of the need both to explain the VCL and also explain exactly why the VCL exists.
I want to make it absolutely clear that I am one hundred and ten percent committed
to the VCL, and have absolutely no doubt that it represents the correct model for
programming at this point in the ongoing development of programming tools and languages.
I will occasionally make statements that explicitly justify some part of the VCL
in the face of possible criticisms. These statements do not in any sense represent
doubts in my own mind about the VCL. I prefer the VCL to OWL or MFC, and I am absolutely
certain that all of the extensions to the C++ language that it introduces are necessary
and represent significant advances for the language.
I am aware that some readers have large quantities of legacy OWL and MFC code
that they want to preserve. I do not take those needs lightly and feel it is my obligation
to state explicitly why changes have been made to the C++ object model.
BCB enables you to use MFC and OWL code, but the only reason this feature exists
is to support legacy code. I personally do not think either MFC or OWL is the best
choice any longer for large programming projects. The introduction of the component,
property, delegation model offers such vast improvements in our ability to write
code, that I don't think it's advisable to use OWL or MFC simply because of the poignant
weight of all that legacy code. This is not a criticism of these two excellent object
frameworks. The point is simply that they do not support components, properties,
or events, and without that support they don't meet my current needs. I simply cannot
imagine that anyone else who works with BCB for any significant length of time could
possibly come to any other conclusion.
I am aware, however, that this is a controversial subject. Over the last four
years I have been using the VCL almost every day. Most of that experience is on the
Delphi side, but I have also been using BCB every day for about five months at the
time of this writing. During the last four years, I have had many occasions to also
use MFC and OWL. After my first six months with the VCL, there was never one single
time that I went back to MFC or OWL without the feeling that I was using a much loved
but outdated system that lacked fundamental features that have become a necessary
part of what I believe to be the best contemporary programming model. This doesn't
mean that I won't use OWL or MFC if it has an existing object in it that I find appealing,
but only that I prefer to use the VCL if that is an option. Indeed, in most cases,
the VCL provides for all my needs.
If a language or programming model does not support properties, components, and
delegation, I personally find it lacking, no matter how many other fine features
it may present to the user. VCL is the right way to go, and it would be absurd to
read my occasional arguments on its behalf as representative of doubts in my own
mind about this programming model.
In a sense, my job would be easier if the VCL were harder to use. It was very
frustrating for me to realize that I had to start leaving behind all that detailed
knowledge about OWL in order to use a system that was so much simpler to use. Wasn't
there something that the VCL was missing? Didn't there have to be a catch? How could
the solution to so many long-term problems turn out to be so simple?
Well, I believe that there isn't a catch, and that the VCL does everything that
OWL or MFC did, but does it better. Of course, OWL and VCL have tremendous depth
in terms of the number of objects in the existing hierarchy, which is one reason
why BCB supports them. You will find, however, the VCL has great depth itself, and
that it supports all the features, if not all the objects, found in MFC and OWL.
In other words, if you find that a particular object you used in OWL is not part
of VCL, you could either use the existing object or create a new one that does the
same thing in the VCL. The VCL supports all the features of OWL, but may not at this
time have duplicates of all its objects. Furthermore, there is tremendous existing
third-party support for the VCL that helps fill in some of the gaps.
For good measure, the VCL adds some new tricks regarding components, properties,
and events that can't be found in either OWL or MFC. The kicker, of course, is that
the VCL produces code that is at least as fast and small as OWL or MFC, but it is
literally about ten times easier to use.
Let me give it to you straight. Back in the old days I loved OWL and thought it
was the greatest thing I had ever seen. I used it all the time and was a fanatical
adherent of that programming model. I now use VCL for everything, and go back to
OWL or MFC only when I absolutely must. To my eyes, the support for components, properties,
and events makes VCL clearly better than OWL or MFC in the same sense that OWL was
clearly better than structured programming. If you love OWL, it is sad to hear this
kind of thing, but components, properties, and events simply bring something new
to the table that OWL and MFC simply can't emulate.
It is hard to accept the rate at which programming languages are evolving at this
time. The burden this places on programmers is immense. The size of this burden is
one of the key reasons I advocate, again and again, doing things the simplest, easiest,
safest way. You have enough to worry about without trying to figure out the last
picayune details of constantly changing standards!
The learning curve associated with the onrush of progress, our attachment to legacy
code, a sentimental attachment to the ANSI committee (of all things!)--none of these
represent a reason to stick with an outdated technology when a clear advancement
in programming tools appears. I obviously have a involvement with Object Pascal.
But I am also a long-term (10 years) C++ programmer, and I know about the deep attachment
we all feel to the rules of this language, and I know what a huge improvement OWL
and MFC represent over structured programming. However, things have changed again,
and personally, I love it! VCL is wonderful. I never waste two seconds thinking about
going back to the old way of doing things.
NOTE: One final note on this subject ought
to go out to readers of my Teach Yourself Windows and Teach Yourself Windows 95 Programming
books. Those books talk about straight C Windows API programming (a la Petzold) without
the aid of any object framework. I do not believe there is anything in this book
that makes the material in those books obsolete.
To be a good Windows programmer you still have to know the Windows API, and you still
have to know about message loops and window procedures. Indeed, in Delphi 2 Unleashed,
I went on for several hundred pages about material of that sort. I do not include
any of that kind of material in this book because there are many C++ programming
books that cover that material. (There were not any, as far as I knew, Pascal books
that covered that material, which is why I included it in Delphi 2 Unleashed.)
Needless to say, I think you ought to use the VCL to write contemporary Windows programs.
However, if you also know the Windows API, you can become a great VCL programmer.
If you don't know the Windows API, you will always be at a loss when using some features
of the VCL, at least until such time as Windows becomes a true object-oriented operating
system. Do you have to know OWL or MFC to become a great VCL programmer? No.
Do you have to know the Windows API in order become a great VCL programmer? Absolutely!
The Windows API material found in my Teach Yourself... books has a very long life
from a technical, if not a commercial, point of view. Assuming that Java does not
take over the world, a knowledge of the Windows API is always invaluable to a contemporary
programmer.
Remember, however, that even the glorious VCL has a shadow over it, in the form of
Java. I never get too bogged down in the details of the VCL or any other framework,
because I never know when this rapidly changing programming world is going to enter
another period of mind-numbing change in which the slow ones get left behind!
The Windows API is the only really complex programming paradigm that is worth learning
in depth, because all the others are likely to change over time. The fact that the
VCL is easy to use is not just a nice feature, it's a necessity in this contemporary
programming world where the only constant is change. I should perhaps add that the
reason you use the VCL instead of the raw Windows API is because you get your work
done in about one-tenth the time. If you use OWL or MFC, your code will be much bigger
than if you use raw Windows API code, but you will also have a better chance of getting
your work done on time. If you want another four- or five-fold increase in productivity,
use the VCL.
Using the VCL
Now that you have heard an advertisement for the VCL, it might help to provide
a few more basic examples illustrating some of the virtues of this programming system.
These are very simple examples that are a bit atypical of the type of code you will
see in this book, or even in the latter portions of this chapter. I feel, however,
that these basic examples are useful when illustrating some of the key features of
RAD programming with the VCL. Their presence ensures that everyone understands the
benefits of visual programming with the VCL.
I will, however, use these basic examples to explore some fairly complex aspects
of the VCL, and especially of the code that executes just before and after the main
form of your application is made visible. In particular, I will look at the code
in the project source file for a typical BCB application that uses the VCL.
The first program I want to discuss uses a standard Delphi component called TShape.
If you open up BCB and drop the TShape component on a form, you will see
that it draws a simple white rectangle on the screen.
Of course, the TShape object can perform more than this one simple trick.
For instance, if you pull down the list associated with the Shape property
in the Object Inspector, you see that you can easily work with ellipses, circles,
squares, and other assorted shapes. Furthermore, if you expand the Brush
property, you can change the shape's color. The pen property enables you
to change the width and color of the outline of a TShape object.
NOTE: Don't forget that you can expand
properties that have a plus sign (+) next to them by double-clicking the property's
name. A Color property always has a dialog associated with it. To bring
up the dialog, double-click the area to the right of the Color property.
This area is called the property editor. (Later in the book I will show how to create
your own property editors and how to use existing property editors.) Select a color
from the dialog, click the OK button, and the color you chose automatically takes
effect.
As just described, it's trivial to change the major characteristics of a TShape
object at design time. However, I spoke earlier about BCB supporting two-way tools.
Anything that you do with the visual tools you can also do in code. It is, of course,
a little more work to make the same changes at runtime that you made at design time,
but the basic principles are still simple. The SHAPEDEM and SHAPEDEM2 programs on
the CD that accompanies this book show you how to proceed.
NOTE: I try to avoid it, but you might
find a few places in the sample programs where I hard-code in the path to a file.
You will probably have to edit these paths to get the program to run on your system.
I go to a great deal of effort to ensure that the code that accompanies this book
works correctly. If you are having trouble running any particular program, check
the readme files found on the CD that accompanies this book. If you still can't get
the program running, go to my Web site and see if there is an update, hint, or bug
report available. My Web site is users.aol.com/charliecal.
At its core, the SHAPEDEM program consists of nothing more than a TShape
object placed on a form, along with two scroll bars and a menu. What's interesting
about the program is the ease with which you can change the size, color, and shape
of the TShape object at runtime.
You can find the code for the program in Listings 2.1 through 2.3. Remember that
if you want to view the source for the project file in your application, you can
select the View | Project Source menu item.
Listing 2.1. The code for SHAPEDEM.CPP.
#include <vcl.h>
#pragma hdrstop
USEFORM("Main.cpp", Form1);
USERES("ShapeDem.res");
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
return 0;
}
Listing 2.2. The header for the
main unit in SHAPEDEM.
#ifndef MainH
#define MainH
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
#include <Dialogs.hpp>
#include <Menus.hpp>
class TForm1 : public TForm
{
__published:
TShape *Shape1;
TScrollBar *ScrollBar1;
TScrollBar *ScrollBar2;
TColorDialog *ColorDialog1;
TMainMenu *MainMenu1;
TMenuItem *Shapes1;
TMenuItem *ShapeColor1;
TMenuItem *FormColor1;
TMenuItem *Shapes2;
TMenuItem *Rectangle1;
TMenuItem *Square1;
TMenuItem *RoundRect1;
TMenuItem *RoundSquare1;
TMenuItem *Ellipes1;
TMenuItem *Circle1;
void __fastcall ShapeColor1Click(TObject *Sender);
void __fastcall FormColor1Click(TObject *Sender);
void __fastcall Rectangle1Click(TObject *Sender);
void __fastcall ScrollBar1Change(TObject *Sender);
void __fastcall ScrollBar2Change(TObject *Sender);
void __fastcall FormResize(TObject *Sender);
private:
public:
virtual __fastcall TForm1(TComponent* Owner);
};
extern TForm1 *Form1;
#endif
Listing 2.3. The code for the
main unit in SHAPEDEM.
#include <vcl.h>
#pragma hdrstop
#include "Main.h"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Shape1->Left = 0;
Shape1->Top = 0;
}
void __fastcall TForm1::ShapeColor1Click(TObject *Sender)
{
if (ColorDialog1->Execute())
Shape1->Brush->Color = ColorDialog1->Color;
}
void __fastcall TForm1::FormColor1Click(TObject *Sender)
{
if (ColorDialog1->Execute())
Form1->Color = ColorDialog1->Color;
}
void __fastcall TForm1::Rectangle1Click(TObject *Sender)
{
Shape1->Shape = TShapeType(dynamic_cast<TMenuItem*>(Sender)->Tag);
}
void __fastcall TForm1::ScrollBar1Change(TObject *Sender)
{
Shape1->Width = ScrollBar1->Position;
}
void __fastcall TForm1::ScrollBar2Change(TObject *Sender)
{
Shape1->Height = ScrollBar2->Position;
}
void __fastcall TForm1::FormResize(TObject *Sender)
{
ScrollBar1->Max = ClientWidth - (ScrollBar2->Width + 1);
ScrollBar2->Max = ClientHeight - (ScrollBar1->Height + 1);
ScrollBar1->Left = 0;
ScrollBar2->Top = 0;
ScrollBar2->Left = ClientWidth - ScrollBar2->Width;
ScrollBar2->Height = ClientHeight;
ScrollBar1->Top = ClientHeight - ScrollBar1->Height;
ScrollBar1->Width = ClientWidth - ScrollBar2->Width;
}
In the next few paragraphs, you'll hear a discussion of how to change the color
of the form, the shape shown on the form, and the size and shape of the object itself.
When you run the SHAPEDEM program, it looks like the screen shot shown in Figure
2.6. Use the program's scrollbars to change the size of the figure in the middle
of the screen. Use the menu to select a new shape for the object and to bring up
a dialog that enables you to change the color of either the form or the shape.
FIGURE
2.6. You can use the scrollbars and buttons
to change the appearance of the SHAPEDEM program's form.
The Project Source: Where Pascal
Meets the C
Before getting into any details about how the ShapeDem program works, it might
be helpful to take one moment to look at the project source. You can access the project
source from the View menu.
This is the place in the book I have chosen to give you a close look at how Borland
blended its Pascal and C++ technology into one product. I will not show much Object
Pascal code in this book, but you will see quite a bit in the next few pages.
At the top of the project source you see code that brings in the VCL:
#include <vcl/vcl.h>
#pragma hdrstop
In many programs, you will not have to add any other include statements
to your program other than this one, except when you want to include other units
from your current project, and even that can be automated through the File Include
Unit Hdr menu option. That's not a hard and fast statement; I only mean to imply
that you don't tend to spend a lot of time adding include statements unless
you want to access obscure features of the Windows API. Many other include
statements will be added to your header files, however, when you drop components
down on a form.
The pragma statement shown here tells the compiler that you only want
to have the VCL in your precompiled headers. Any additional files should be recompiled
each time you do a build or a make. This is done for the sake of efficiency, because
your own header files are likely to change on a regular basis.
The next few lines tell the project manager to bring in forms or resources:
USEFORM("Main.cpp", Form1);
USERES("ShapeDem.res");
You would not normally add this kind of code yourself, but would ask the visual
tools to insert it for you. In this case, the tool you would use is the Project Manager
from the View menu. However, you don't have to use the Project Manager; you can make
this changes manually if you wish, and the Project Manager will pick up on your work.
Here is the WinMain block for your program:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
return 0;
}
As you can see, BCB assumes you won't be using any of the parameters passed to
it. In particular, the VCL provides a global variable called HInstance,
which provides the HInstance for your application. The HPrevInstance
variable is never used in Win32 programming. Parameters passed to your program are
available in the form of a VCL function called ParamStr, and a variable
called ParamCount. To get at the name and path of your executable, access
ParamStr(0), to get at the first parameter passed to it, access ParamStr(1),
and so on. ParamCount contains the number of parameters passed to your executable.
For instance, the following code pops up a message box showing the name and path
of your executable:
void __fastcall TForm1::Open1Click(TObject *Sender)
{
ShowMessage(ParamStr(0));
}
NOTE: Wary C++ programmers may be concerned
about the fact that the VCL has gotten at the HInstance and program parameters
before they reached WinMain. Don't worry, there is no huge subsystem underlying
your entire program! However, a few things do happen before WinMain is called,
just as a few things happen in all C++ compilers before WinMain is called.
This is a case where it might help to talk about how Object Pascal handles program
startup. Object Pascal simply did not support WinMain on the grounds that
it was faster and more convenient to perform processing of HInstance and
the program parameters without setting up a stack frame for a function call. The
Object Pascal system is faster and more efficient because you don't have to push
things on the stack before calling a function named WinMain. On the other
hand, the overhead of calling WinMain with four parameters is hardly significant
in the face of the time it takes to load a contemporary Windows program into memory.
Of course, BCB provides a WinMain for you so you will be able to compile
standard C++ programs without modification.
The key point to notice here is that the VCL provides extra support for you without
affecting the performance of your program and without changing the way that C++,
or standard Windows programs, operate. This is the right way to handle the interface
between C++ and Object Pascal. No changes to C++, no impact on performance, no changes
to the standard Windows programming model, and yet additional features are provided
for the programmer in terms of ParamStr and the global HInstance.
After getting to WinMain, the program uses the pre-initialized Application
object to get your program up and running:
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
I will discuss each of these calls in their own sections of this chapter. Brace
yourself, because I am going to step you through the Object Pascal code that underlies
each of these calls.
Initializing the Application
The first call in WinMain performs some initialization:
Application->Initialize();
The two most important tasks performed by this code involve OLE and database code.
If there is database programming in your application, the database code starts an
object called TSession. If there is OLE code in your program, the program
may call a method that updates the Registry automatically so that your program is
a properly registered automation server. If the code uses neither technology, nothing
happens in the call to initialize.
Here is the code for the Initialize method of TApplication as
it appears in Forms.pas:
procedure TApplication.Initialize;
begin
if InitProc <> nil then
TProcedure(InitProc);
end;
If you want to step through this code, simply copy Forms.pas, Controls.pas,
Classes.pas, and VCL.INC from the BCB\SOURCE\VCL subdirectory
into your project directory and add Forms.pas, Controls.pas, and
Classes.pas to your C++ project. To add the files, bring up the Project
Manager from the View menu, click the plus button, and use the drop-down list from
the Files of Type section to browse for files with a .pas extension. Next,
select Forms.pas, Classes.pas, and Controls.pas, and close
the Project Manager. This technique is probably preferable to bringing in the source
to the whole VCL by adding the CBuilder\SOURCE\VCL to the include or library
path for your project.
When you step through this Object Pascal code in the BCB integrated debugger,
you will find that InitProc is never called, because it is set to NULL
unless you bring in database code or OLE automation code. Needless, to say, it takes
one line of assembly code to test for NULL, so there is no significant overhead
involved here other than the call to the Initialize method itself.
Here is the code at the bottom of the OLEAuto unit that would initialize
InitProc if you used OLEAutomation in your program:
initialization
begin
OleInitialize(nil);
VarDispProc := @VarDispInvoke;
Automation := TAutomation.Create;
SaveInitProc := InitProc;
InitProc := @InitAutomation;
end;
finalization
begin
Automation.Free;
OleUninitialize;
end;
end.
This call starts by initializing OLE and then setting up a dispatch point for
use when making calls into IDispatch. Next, it allocates memory for the
Automation object, calls its constructor, and finally points InitProc
at the proper method after saving the previous value of the function pointer. Of
course, none of this code will be called unless you are using OLE Automation in your
program through the routines found in the OleAuto unit.
Creating Forms
The second call in WinMain creates the MainForm for your application:
Application->CreateForm(__classid(TForm1), &Form1);
You already have the Forms unit linked into the project, so you can step
into this code without any further work. However, if you want to get into more detail
here, you can also add Classes.pas and Controls.pas to your project.
However, there are very few users of the VCL who really need to know what happens
in either controls or classes.
Here is the call to CreateForm:
procedure TApplication.CreateForm(InstanceClass:
TComponentClass; var Reference);
var
Instance: TComponent;
begin
Instance := TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
try
Instance.Create(Self);
except
TComponent(Reference) := nil;
raise;
end;
if (FMainForm = nil) and (Instance is TForm) then
begin
TForm(Instance).HandleNeeded;
FMainForm := TForm(Instance);
end;
end;
The vast majority of this code does nothing but initialize variables or check
for errors.
The base VCL object is called TObject. All VCL objects descend from TObject
by definition. It is impossible to create a VCL object that does not descend from
TObject, because any object that does not descend from TObject
is not part of the VCL. The call to TObject.NewInstance does nothing more
than allocate memory for an object and return a pointer to it, as you can see from
viewing this call in System.pas:
class function TObject.NewInstance:TObject;
asm
PUSH EDI
PUSH EAX
MOV EAX,[EAX].vtInstanceSize
CALL _GetMem
MOV EDI,EAX
MOV EDX,EAX
POP EAX
STOSD { Set VMT pointer }
MOV ECX,[EAX].vtInstanceSize{ Clear object }
XOR EAX,EAX
PUSH ECX
SHR ECX,2
DEC ECX
REP STOSD
POP ECX
AND ECX,3
REP STOSB
MOV EAX,EDX
POP EDI
end;
Needless to say, this is probably the only place you will ever see anyone allocate
memory for an object by calling NewInstance. As a rule, the constructor
for an object will call NewInstance automatically. Here is the standard
code for creating an object:
TMyObject *MyObject = new TMyObject();
This will call NewInstance for you automatically, and it would be madness
to proceed in any other fashion.
NOTE: TObject.NewInstance is
what is called in Object Pascal a class method and what C++ implements as a static
method. Class methods and static methods can be called without an object instance;
that is, they can be called before you allocate memory for an object or call its
constructor. Class methods could have been implemented as functions that are associated
with a class, and indeed, there is no significant difference between a function associated
with a class and a class method. However, it is syntactically useful from the user's
point of view to include them in a class declaration, because it makes the association
between the class and the method obvious. In other words, class methods are aesthetically
pleasing, and they help you create logical, easy-to-read code. Besides NewInstance,
the following methods of TObject are all class methods: ClassName,
ClassNameIs, ClassParent, ClassInfo, InstanceSize,
InheritsFrom, MethodAddress, and MethodName.
These methods are segregated out of the BCB declaration for TObject and
declared as a class with the somewhat intriguing name TMetaClass. This metaclass
is a trick that allows C++ to get along with the VCL and its associated RTTI. TMetaClass
plays no significant role in standard BCB programming and is designed primarily for
the use of the compiler team itself. You may have occasion to use this metaclass
when working with RTTI, but as a rule, it lies outside the realm of standard BCB
programming. If you have the time and inclination to pursue this matter, the whole
class is declared and implemented in SYSDEFS.H.
For instance, the ClassType method of TObject returns a variable
of type TClass, and TClass is defined as a pointer to TMetaClass.
You can use variables of this type to perform the same kinds of comparisons you would
perform on standard C++ classes with typeid.
The most important call in TApplication CreateForm is to Instance.Create(Self).
It is the call that ends up calling the constructor for the main form, as well as
calling the Windows API CreateWindowEx function. If you step into this code
with the debugger, you will find that it ends up stepping right into the constructor
for your main form:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
The last few lines of code in the CreateForm method set the variable
FMainForm to the current form if it has not already been assigned.
The main form for your application will be the one that appears first when someone
launches your executable. As you can see from examining the code in CreateForm,
the first object passed to CreateForm will be the main form for that application:
if (FMainForm = nil) and (Instance is TForm) then
begin
TForm(Instance).HandleNeeded;
FMainForm := TForm(Instance);
end;
Each project that you create can have zero, one, or more forms. You can add forms
to a project from the File menu or from the File | New menu choice. If you add three
forms to a project, this is what the project source looks like:
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->CreateForm(__classid(TForm2), &Form2);
Application->CreateForm(__classid(TForm3), &Form3);
Application->Run();
In this case, Form1 will be the main form for the application. If you
change the order of these statements, another form could become the main form:
Application->Initialize();
Application->CreateForm(__classid(TForm3), &Form3);
Application->CreateForm(__classid(TForm1), &Form1);
Application->CreateForm(__classid(TForm2), &Form2);
Application->Run();
In the preceding code, Form3 is now the main form for the application.
Normally, I do not edit the project source for my application directly. Instead,
I go to the Options | Project menu item and use the Forms page from the Project Options
dialog to make these kinds of changes. Once again, this is a two-way tool, and you
can do things visually via the Project Options dialog or you can do things manually
in code. It's up to you to make the decision.
Calling Run: Finding the Message
Loop
The last call in WinMain is to the Run method:
Application->Run();
TApplication.Run does a few initialization chores, and then calls a method
called HandleMessage that in turn calls ProcessMessage:
function TApplication.ProcessMessage: Boolean;
var
Handled: Boolean;
Msg: TMsg;
begin
Result := False;
if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end
else
FTerminate := True;
end;
end;
This is the standard message loop that lies at the bottom of all Windows applications.
As you can see, it calls TranslateMessage and DispatchMessage just
like every other message loop in every other Windows application.
The HandleMessage routine that calls ProcessMessage also ends
up calling a method named Idle. If you hook the OnIdle handler
to TApplication, you can get a chance to perform background tasks while
your application is running. It is generally not a good idea to respond to OnIdle
events unless you are absolutely positive you know what you are doing. On the other
hand, I would not suggest starting up a second PeekMessage loop in your
application, so if you have to do background processing, I would indeed do it in
response to OnIdle.
You now know what happens when a VCL application is first launched. I've shown
it to you in such depth for two reasons:
- You need to know this information if you are going to use BCB for serious work.
You have to understand how your application is launched, where the message loop is,
and how forms are allocated.
- There has to be some time when you actually see the place where C++ and Object
Pascal meet. So now you've seen it. The point I hope you take home from this is that
there just plain isn't any real difference between C++ and Object Pascal. There may
be a difference between ANSI C and ANSI Pascal, but C++ and Object Pascal are the
same. Certainly there is no significant difference in the way the two languages perform
or in the capabilities of the two languages. It should also now be obvious that the
compiler just doesn't care which language you use. I mix and match C++ and Object
Pascal all the time. I know both languages well enough so half the time I am not
even conscious of which language I'm using. You can find a language bigot on every
street corner. What's rare is to find someone who can look beneath the surface and
see that at bottom the two languages are virtually identical. The differences are
almost all superficial, and they tend to cancel each other out. C++ does some things
better than Object Pascal, and Object Pascal does some things better than C++. Who
cares? The point is they are both great languages. Now you can use them both together
without penalty, so you can take the best from both worlds.
Creating the ShapeDem Program
It's time now to come back to the original purpose of this section of the chapter,
which is to examine the ShapeDem program. To create the program yourself, start by
dropping down a TMainMenu and a TColorDialog. The TColorDialog
is found on the Dialogs page of the Component Palette, while the TMenu is
on the Standards page. Now create a menu heading called Options and beneath it two
menu items with captions that read Shape Color and Form Color.
NOTE: I briefly introduced using the Menu
Designer in Chapter 1. Remember that you can get help on most (in a perfect world
it would be all) BCB components, code elements, or tools by selecting the item in
question and pressing F1.
After closing the menu designer, double-click the menu item you made called Form
Color to create a method in the editor that looks like this:
void __fastcall TForm1::FormColor1Click(TObject *Sender)
{
if (ColorDialog1->Execute())
Form1->Color = ColorDialog1->Color;
}
When you run the program, the code shown here pops up the ColorDialog,
as shown in Figure 2.7.
FIGURE
2.7. The Color Dialog gives the user an
easy way to select a valid color at runtime.
If the user clicks the OK button in the form, the following line of code is executed:
Form1->Color = ColorDialog1->Color;
This line of code sets the Color property for Form1 to the color
that was selected by the user inside of ColorDialog1.
The technique just shown can be used to change the color of the TShape
object. All you need to do is drop down a TShape object from the additional
page, and then associate some code with the Shape Color menu item:
void __fastcall TForm1::ShapeColor1Click(TObject *Sender)
{
if (ColorDialog1->Execute())
Shape1->Brush->Color = ColorDialog1->Color;
}
What could be simpler?
You should now run the SHAPEDEM program so that you can see how easy it is to
change the color of the elements on the form. Of course, you don't have to give the
user the exclusive right to control all the elements of your program. Sometimes you
can take the initiative. For instance, you could change the color of your form or
of an element on your form in order to focus the user's attention on a particular
part of the screen.
Notice that the code written here is all but self-documenting. Anyone with even
the slightest acquaintance with programming can just glance at this procedure and
determine what it does. Assuming you have more than a passing acquaintance with programming,
here is how to translate the code into English: "If the user clicks on a visual
element on Form1 that is called ShapeColor1, that visual element
will delegate an activity to Form1. That activity consists of popping up
a color dialog and asking the user to make a selection. If the user chooses the OK
button, the color of Shape1 is set to the color selected in the TColorDialog
object."
What do I mean when I say that the ShapeColor menu item "delegates"
an activity to Form1? Well, this means that the ShapeColor TMenuItem
object does not itself handle being clicked, but instead allows the form to decide
what happens when someone clicks it. It delegates the job to the main form.
The delegation model works through events, which are listed in the Object Inspector
on the page sitting next to the properties for an object. Some people call events
closures, though I regard that as a very technical term with certain platform-specific
ramifications that I have never fully explored. As a result, I will usually play
it safe and call events nothing more than events and leave it at that. The delegation
model is implemented through events, and events are explored in depth in Chapter
3, "Events," and Chapter 4, "Exceptions."
In the beginning, it is probably simplest to think of events as being similar
to standard Windows messages, such as WM_CREATE, WM_COMMAND, or
WM_VSCROLL. Indeed, handling standard Windows messages is one of the functions
of events. However, you will see that events can also delegate to another control
tasks that you usually could not handle unless you subclassed a component such as
an edit control, or else descended from an object that wrapped a control, as you
would in OWL or MFC.
Perhaps an acceptable first crack at defining events would be to say that: "Events
allow you to delegate tasks from one component to another component so that you do
not have to subclass the first component, nor respond inside a window procedure to
messages that the first control generates. In particular, events are usually delegated
by the controls on a form to the form itself."
The advantages of the delegation model are threefold:
- They do not force you to subclass a control, nor inherit from a class in order
to override one of its properties. For instance, you can change an edit control's
behavior without subclassing the control or descending from an OWL or MFC type object
that wraps the control.
- They enable you to forgo the ugly, lengthy, and confusing switch statement
found in standard window procedures.
- It provides for contract-free programming. You can do whatever you want in an
event handler. There are no rules binding what you can and cannot do inside an event
handler. Any code you can write legally elsewhere in a program you can also write
in an event handler.
It makes sense that it should not be very difficult to change the color of an
object found on one of the forms you create. But using scrollbars to change its shape
at least appears to be a more difficult task. In fact, experienced Windows programmers
know that using scrollbars in a Windows program can be a fairly difficult task, one
that requires you to trap and parse a number of messages in a complex switch
statement. BCB, however, reduces the entire task of responding to a scrollbar to
a single line of code.
To get started, first drop two scrollbars on the screen and set the Kind
property of one of them to sbHorizontal, and the Kind property
of the other to sbVertical. Now, turn to the events page of the Object Inspector
and create a method for the OnChange event of each scrollbar. Fill in the
methods with two lines of code so that they look like this:
void __fastcall TForm1::ScrollBar1Change(TObject *Sender)
{
Shape1->Width = ScrollBar1->Position;
}
void __fastcall TForm1::ScrollBar2Change(TObject *Sender)
{
Shape1->Height = ScrollBar2->Position;
}
The code shown here sets the Width and Height of the TShape
object to the current position of the thumb on the scrollbar. Clearly it is extremely
easy to write BCB code that performs a task, which would be relatively complex to
execute if you had to work directly with the Windows API. The VCL always makes you
feel as though you are working directly with an object, and tries to hide the complexities
that are introduced by the Windows API or by standard object frameworks.
NOTE: Note in particular that you don't
have to first set a property of a control and then ask it to redraw itself! This
magic is the result of the set method associated with the Height property
of Shape1. I will explore the Set and Get methods of properties
later in this chapter.
You use the program's menu to change the shape of the TShape component.
In particular, a portion of the main menu for the program should have the following
items on it:
Rectangle1: TMenuItem
Tag = 0
Caption = `Rectangle'
Square1: TMenuItem
Tag = 1
Caption = `Square'
RoundRect1: TMenuItem
Tag = 2
Caption = `RoundRect'
RoundSquare1: TMenuItem
Tag = 3
Caption = `RoundSquare'
Ellipes1: TMenuItem
Tag = 4
Caption = `Ellipse'
Circle1: TMenuItem
Tag = 5
Caption = `Circle'
The Tag field is a special field associated with all components that
can be set to an integer value. The VCL has no use for this field; it is designed
explicitly for you to use as you wish, for whatever purpose you want.
All six menu items should be associated with the same event handler through their
OnClick method. You can create the original handler by open up the Menu
Designer, turning to the Events page in the Object Inspector, and double-clicking
the OnChange event. If you then select the OnChange event for the
other menu items, you will see that it opens up into a combo box from which you can
select all the compatible events. For each of the other five menu items, select the
event you created for the first menu item. The event itself should look like this:
void __fastcall TForm1::Rectangle1Click(TObject *Sender)
{
Shape1->Shape = TShapeType(dynamic_cast<TMenuItem*>(Sender)->Tag);
}
I will explain what this code means over the course of the next few paragraphs.
NOTE: Just now I said that the events
that appear in the drop-down combo for the Events page of the Object Inspector must
be compatible with a particular event. This means that they must have the same signature,
which is yet another term that needs explanation! The signature of an event is found
in the header for the method handler associated with a particular event, and in particular
it is represented by the parameters passed to the handler. For instance, the signature
for the Rectangle1Click method is represented by the fact that it is a method
that returns nothing and which accepts a single parameter of type TObject *.
Methods that look like this are of type TNotifyEvent. Here is the declaration
for TNotifyEvent as it appears in CLASSES.HPP:
typedef void __fastcall (__closure *TNotifyEvent)(System::TObject* Sender);
When you see this signature for an event, you can assume that Sender
contains an instance of the object that sent the event. For instance, in the case
currently under consideration, it is a TMenuItem that sent the event, and
the specific TMenuItem that sent the message is passed in the Sender
parameter.
The items in the menu designate each of the possible shapes the TShape
component can assume. You can find these words listed in the online help under the
listing for TShapeType. Or, if you want to go back to the original source
code, you find the following enumerated type:
enum TShapeType { stRectangle, stSquare, stRoundRect,
stRoundSquare, stEllipse, stCircle }
NOTE: You can also access the names of
the members of enumerated type by using the GetEnumName and GetEnumValue
functions from the TypInfo unit, which is pronounced "tip-info."
In this particular case, you need to write only one line of code in response to
the event that occurs when a user clicks a TMenuItem:
Shape1->Shape = TShapeType(dynamic_cast<TMenuItem*>(Sender)->Tag);
This line of code sets the Shape1->Shape property to the shape that
the user has selected in the combo box. The code works because of the correspondence
between the ordinal members of an enumerated type and the numerical value you assigned
to the tag property of a menu. In other words, the first element in an enumerated
type has the value zero, as does the first tag property.
You know, and I know, that the Sender variable contains an instance of
the TMenuItem item class. However, the wonders of polymorphism allow BCB
to declare this variable to be of type TObject. That way, a TNotifyEvent
can accept objects of any type. (Polymorphism will be addressed later in the book
in Chapter 21, "Polymorphism.")
The programmer's knowledge of the Sender parameter needs to be shared
with the compiler. In other words, you and I need to have some way of telling the
compiler that the object is not really of type TObject, but that it is a
just a polymorphic disguise for a TMenuItem. This is an important step because
the TObject class does not have a Tag field, and the TMenuItem
class does have a Tag field. In this case, it is absolutely necessary to
get at the Tag field because it contains the code information about the
shape the user wants to select.
To cast a TObject as a TMenuItem you can use the dynamic_cast
syntax. This syntax can be used as part of RTTI to test whether a component is of
a particular type, or it can be used to actually make the cast. In this case I am
daring and make the cast without bother to test it first. This is safe in this case
because I know the only object that can be passed here is a TMenuItem. However,
if I wanted to be extra careful, I could write the following:
if (dynamic_cast<TMenuItem*>(Sender))
Shape1->Shape = TShapeType(dynamic_cast<TMenuItem*>(Sender)->Tag);
This would ensure that no exceptions were raised if the cast should happen to
fail, which it won't in this particular case.
The final part of the ShapeDem program that is worthy of discussion involves
making sure that the scrollbars cling to the bottom and right edges of the control.
To do this, you need to respond to WM_SIZE messages, which come to BCB programmers
under the friendly guise of an OnResize event:
void __fastcall TForm1::FormResize(TObject *Sender)
{
ScrollBar1->Max = ClientWidth - (ScrollBar2->Width + 1);
ScrollBar2->Max = ClientHeight - (ScrollBar1->Height + 1);
ScrollBar1->Left = 0;
ScrollBar2->Top = 0;
ScrollBar2->Left = ClientWidth - ScrollBar2->Width;
ScrollBar2->Height = ClientHeight;
ScrollBar1->Top = ClientHeight - ScrollBar1->Height;
ScrollBar1->Width = ClientWidth - ScrollBar2->Width;
}
This code uses third grade math (the only kind I ever understood!) to ensure that
the scrollbars are allowed as tall or as wide as the form, no matter how it is stretched
or pulled by the user. Furthermore, it ensures that the Min and Max
property for the control ranges from zero to the exact size of the current client
window. This ensures that you can use the OnChange event for the scrollbar
to make the TShape object as large as the client window, but no larger.
Notice also that the TForm object has properties called ClientWidth
and ClientHeight. These properties calculated the client size of the current
window; that is, the size of the window minus the menu, caption, and frame. If this
was done for you, you would have to write code that looked something like this:
int Menu, Caption, Frame;
Caption = GetSystemMetrics(SM_CYCAPTION);
Frame = GetSystemMetrics(SM_CXFRAME) * 2;
Menu = GetSystemMetrics(SM_CYMENU);
ScrollBar1->Left = 0;
ScrollBar2->Top = 0;
ScrollBar1->Max = Width;
ScrollBar2->Max = Height;
ScrollBar2->Left = Width - Frame - ScrollBar2->Width;
ScrollBar2->Height = Height - Frame - Caption- Menu;
ScrollBar1->Top = Height - ScrollBar2->Width - Frame - Caption - Menu;
ScrollBar1->Width = Width - ScrollBar2->Width - Frame;
The point here is that without having to know about GetSystemMetrics
and all its associated constants, you can still write code that calculates the size
of the TShape object down to the last pixel.
A First Look at RTTI and TypInfo
If you look on the disk, you will find a second copy of the ShapeDem program,
called ShapeDem2, that uses a combo box as well as a menu to let the user select
the current shape of the object. For instance, you can drop down the combo box and
pick a shape such as stCircle or stSquare. These shapes have funny-looking
Hungarian squiggles prefixed to them because they are taken directly from the source
code of the program at runtime rather than typed in by the programmer at design time.
In the next few paragraphs I explain how to use that control, primarily because it
also contains an interesting description of how to use Run Time Type Information.
You would think that you need to explicitly type in the names of the shapes that
appear in the combo box. For instance, you might assume you need to manually type
in the words stCircle and stSquare, and so on. These are the same
names you see listed in the Object Inspector for the Shape1 object under
the property called Shape, and they are the same words that appear in the
combo box at runtime. In other words, if you highlight Shape1 on the form
and look at the Shape property in the Object Inspector, you find a list
of the possible shapes that can be associated with this object. These are the same
shapes you should list in the combo box.
As I said earlier, it's not possible to get access to the Object Inspector at
runtime. As a result, if you wanted to explicitly type in the names, you would need
to first pop open the Property editor for the Items property, and then manually
type these names. To get started, first highlight the combo box on the form by clicking
it. Then, double-click the right side of the Items property in the Object
Inspector, or you can click the [...] button on the Strings property once.
This pops up a String list editor, as shown in Figure 2.8.\
FIGURE
2.8. The String list editor enables you
to type in a set of default names that appear in a combo box.
NOTE: In the bottom-left corner of the
String list editor is a button that says "Code Editor." If you press this
button you can edit your list inside the regular editor for the IDE. This can be
especially useful with SQL query statements, which actually provide syntax highlighting.
You access the SQL list editor from inside a TQuery component as explained
in Chapter 10, "SQL and the TQuery Object."
The actual items that you would type into the String list editor are shown next.
Be sure to type them in exactly as shown, and in the identical order.
stRectangle
stSquare
stRoundRect
stRoundSquare
stEllipse
stCircle
Now when you run the program, the user can drop down this list and select a shape.
Here is the code associated with the OnClick event for the combo box:
void __fastcall TForm1::ComboBox1Change(TObject *Sender)
{
Shape1->Shape = TShapeType(ComboBox1->ItemIndex);
}
As you can see, there is no significant difference between this code and the code
written in response to a click on the shapes listed in the menu. Of course, in this
case you don't have to perform a dynamic cast, but that is a mere bagatelle.
The interesting thing about this second version of the ShapeDem program is the
code that appears in the constructor for the form:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
int i;
PPropInfo PropInfo = GetPropInfo(PTypeInfo(ClassInfo(__classid(TShape))),"Shape");
for (i = 0; i < 6; i++)
ComboBox1->Items->Add(GetEnumName(PropInfo->PropType, i));
... // Code irrelevant to this example omitted here.
}
This code uses RTTI to retrieve the names of the enumerated type that underlies
the Shape property for the TShape object.
All VCL classes are illuminated with type information. You can find out virtually
anything you want about these published properties of a class at runtime merely by
asking through one or more of the functions in the TypInfo unit.
RTTI is an essential part of RAD programming because there must be a way for the
Object Inspector to query an object about its published properties. For instance,
the Object Inspector has to list the names of the TShapeType so that you
can select them from the Property editor for the Shape property. The code
shown in the constructor for the main form of this application uses the same technique
to retrieve this information that the Object Inspector uses. It would not do to have
BCB developers type in this information manually before releasing the project because
the Object Inspector must be able to retrieve this information for any component,
including the ones that you create or find after purchasing the product.
Are classes illuminated with RTTI larger than non-illuminated classes? You bet!
RTTI is bought at a price. However, it is a price that I am willing to pay for two
reasons:
- It makes RAD possible, as described previously in the text that explains how
the property types are listed in the Property editor.
- It enables me to query objects at runtime. Querying objects at runtime gives
me the ability to create generic code that will work automatically, regardless of
the type of object I pass to it. For instance, the RTTI example using dynamic_cast
shown previously explains how you can safely test the type of an object before performing
an action on it. You will see many examples of that type of code in this book, including
cases where you can pass an entire form to a routine, and ask that routine to perform
some function on all the controls of a certain type on that form. Generic code of
that type can be tested once and then used over and over again with multiple objects.
The ability to talk to an object and ask it its type gives us the ability to manage
objects automatically. This can be especially useful in Ole Automation, and particularly
in DCOM. For instance, you can start wandering about on a network, looking for objects.
If you find one, you can use RTTI to ask it questions about itself, and to query
its capabilities!
Using the RGB Function
In this section I take a very quick look at a second program that uses many of
the same controls from the first program. This is just an example, but it is interesting
in that it involves using at least one simple Windows API function, or rather, macro.
It's important to understand that you can, of course, call the Windows API whenever
you want.
Whenever a TShape component is painted, its interior and border are drawn
in particular, predefined colors. By default, these colors are white and black, respectively.
More specifically, the interior of the ellipse is filled with the color of the currently
selected brush. You can change this color by making an assignment of the following
type:
Shape1->Brush->Color = MyNewColor;
The RGBSHAPE program on your disk shows how you can get very specific
control over the colors of an object that you paint to the screen. The letters RGB
stand for red, green, and blue; each of the these colors makes up one of the colors
passed to the Windows API RGB macro itself:
COLORREF RGB(
BYTE bRed, // red component of color
BYTE bGreen, // green component of color
BYTE bBlue // blue component of color
);
The parameters passed to this function describe an intensity to be assigned to
one of these three colors. These numbers always exist within a range between 0 and
255.
If you pass the RGB function the following parameters, it will return a long integer
representing the color red:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
int Red;
Red = RGB(255, 0, 0);
Shape1->Brush->Color = TColor(Red);
}
The Color property of a TBrush object is of type TColor,
but TColor is nothing but an enumerated type ranging over all the possible
values that can be returned by the RGB macro.
NOTE: You can call the entire Windows
API from inside BCB. However, most of the time your calls to the Windows API will
be mapped through WINDOWS.HPP, which is a wrapper around the Pascal version
of these calls. The Object Pascal team did not translate all the headers for the
Windows API. As a result, when you call some of the more obscure or some of the most
recent Windows API functions, you may have to explicitly include the header files
for these calls. For instance, there is no Pascal translation of the headers for
DirectX. As a result, you must include DDRAW.H, and so on in your project
if you want to make calls to DirectDraw or other DirectX functions. The
vast majority of Windows API calls are in WINDOWS.HPP, however, and that
unit is included in all your projects automatically, so you don't have to #include
anything in your projects. Needless to say, all Windows API calls are just mappings
into a DLL, and it doesn't matter one wit whether you are mapped into the call through
Pascal or C++. The compiler does the exact same thing in both cases, and there is
no difference in performance, size, and so on.
Here's how you get the colors green and blue:
Green = RGB(0, 255, 0);
Blue = RGB(0, 0, 255);
If you combine these three colors in various ways, you can produce particular
shades. For instance, if you drop a button into a form and respond to a click on
the button with the following code, you draw a bright yellow ellipse on the screen:
Shape1->Brush->Color = RGB(255, 255, 0);
Shape1->Shape = stEllipse;
To achieve the color gray, pass in the following parameters:
Gray = RGB(127, 127, 127);
To get a fleshlike color, enter
Skin = RGB(255, 127, 127);
You see how it works. Remember that the first parameter controls the amount of
red in the final color, the second the amount of green, and the third the amount
of blue. RGB: red, green, blue!
The RGBSHAPE program has a TShape component, three labels, three scrollbars,
and three edit controls.
The RGBSHAPE program has only one method:
void __fastcall TForm1::ScrollBar1Change(TObject *Sender)
{
Shape1->Brush->Color = RGB(ScrollBar1->Position,
ScrollBar2->Position,
ScrollBar3->Position);
Edit1->Text = IntToStr(ScrollBar1->Position);
Edit2->Text = IntToStr(ScrollBar2->Position);
Edit3->Text = IntToStr(ScrollBar3->Position);
}
This method first uses the current positions of the scrollbars to assign a color
the TShape objects brush. To make this work correctly, I set the Max
property for each scrollbar to 255. When the color has been drawn on the screen,
I show the actual numbers passed to the scrollbar in the edit components.
The point of the RGB program is to give you a graphical representation of the
way the RGB function works. You might also find that this program helps you choose
colors that you want to use in your own programs.
NOTE: When working with the RGBSHAPE program,
some users may find that Windows cannot create pure tones for some colors, but instead
creates a kind of patchwork that approximates the shade described by the parameters
passed to the RGB function. However, you can generally get pure tones if you set
each of the parameters to 0, 128, or 255. Numbers halfway between 0 and 128 also
usually produce pure tones. Of course, the actual results you see depend on whether
you are using a 16-color card, 256-color card, or some video card that offers many
thousands of colors. You also need to have the correct video drivers in place. For
instance, you don't want to be using a powerful video card and get only 16 colors
from it simply for want of the right driver! If you suspect your card is not performing
correctly, get on the Web, visit the home page of the company that makes your video
card or computer, and download the latest drivers. In Windows 95, you can usually
simply unzip the drivers into a temporary directory, right click the desktop, and
select Properties | Settings | Change Display Type. When Windows asks for your new
drivers, simply point it at the directory where you unzipped the files and it will
add them to the system automatically. Be sure to test your work before you restart
Windows!
Listing 2.4. The code for the
main unit in the RGBSHAPE program.
#include <vcl.h>
#pragma hdrstop
#include "Main.h"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::ScrollBar1Change(TObject *Sender)
{
Shape1->Brush->Color = RGB(ScrollBar1->Position,
ScrollBar2->Position,
ScrollBar3->Position);
Edit1->Text = IntToStr(ScrollBar1->Position);
Edit2->Text = IntToStr(ScrollBar2->Position);
Edit3->Text = IntToStr(ScrollBar3->Position);
}
Summary
In this chapter you have been introduced to the basic facts about Borland C++Builder.
In particular, you learned about the environment, manipulating components, and some
of the basic tricks of RAD programming. You also had a brief introduction to some
advanced topics such as RTTI.
By now the boards are clear to head into some more technical subject matter, such
as the examination of extensions to the language featured in the next chapter. After
that you will look at events, exceptions, graphics, and sharing code with Delphi.
By the time you have finished all these sections, you will have a solid base from
which you can launch an exploration of how to use BCB to create cutting-edge Windows
applications.
There is so much information to give out in a book like this that I sometimes
forget to mention how much I love this programming environment. All the information
I am presenting here adds up to a tool that is more powerful, more flexible, and
easier to use than any other C++ compiler on the market. Furthermore, this is just
the 1.0 version of a product that should become the environment of choice for serious
programmers during the next few years.
|